Flink SQL 的性能调优之旅!
👇点击“大数据技术团队”,一键关注
回复关键词 达摩院 可获取达摩院2022十大科技趋势报告PDF
最近,正在使用 SQL,我们正在使用 SQL 连接了几个维度表以实现查询记录的一些实验。更多性能我们想带上执行。进一步,看看这是否以及如何实现。提示我们的工作人员与我们展示不同的代码(证明),以及未来的预期。
这个实验中的一个实验者为我们在 Azure Kubernetes 设置中执行的实验提供了支持,这些之前的博客文章包含 10 个实例来自 Standard_D2_v3 实例(每个 CPU)、Ververica Platform 2.0、Flink 1.10 和并行性即实例,每个具有 1 个将展示在稳定状态组合的所有源 CPU 的平均访问量。我们期间的平均表现网址,即numRecordPerSecond,基准 15 分钟的基准代码的最后 5 分钟。以下示例的源分钟可从检索:
https ://github.com/ververica/lab-sql-vs-datastream 。
SQL查询
首先,让我们看一下来自我们周围的查询。下面概述了一个真实的流式 SQL SQL 。中的数据作为每个人最多的100个字符字符串):
在这种情况下,我们决定在这种情况下,我们决定在运行时的这个重要表状态连接的地方:fact_table如果您进一步研究时间连接( LATER AL TABLE语句和围绕dim_table1 、dim_table5的包装行与...进行连接表),您看到可能派上用场的事件时间连接支持。但是,为了简单起见,我们这里没有使用它。
对于基础测试,我们将每个维度数码输入流限制为每秒1,000个事件,并使事实流不受限积。
MyLateralTableJoin.java中的 数据流代码 为每个输入表创建一个流式传输源,输出转换为附加DataStream ,该附加 DataStream 通过传输到DiscardingSink中。在 Flink 1.10 中设置这个 SQL 使用管道计划:使用旧的 Flink 器或使用新的 Blink 计划。让我们有什么区别。
旧/Flink 规划器
当前(从 Flink 1.10 开始)默认使用旧的规划器,或者可以通过手动设置
下面的 SQL 操作源,其中包含一些链式,将 DataStream 转换为用于转换为表并在每次连接后创建选定的查询列子集:
开箱即用,这给了 84,279 个事件的稳定收益。
新/blink计划器
Flink 的新 Blink 规划实现实现了增强功能,例如改进的 Stream 功能,同时查看时间,在使用表中化/反例化/反例化进程。
创建的作业外观与旧计划没有不同的图,并且具有相似的概念与主题:
在提高这种情况下,只需要立即通知 89、B 链接 04 到目标器 88 个事件 (+5%)。
对象重用
实际操作由两个链组成:源附加链接的创建式的转换/处理字段选择(选择)之间的数据对象交换的,你会注意到性链式存储算子之间的数据传输会经历一个防御的序列化/反序列化/复制阶段,以防将对象在一个算子中,同时在下一个这种行为算子会影响修改程序,会影响流式,并且通常可以启用重用可能很危险,但在Flink的 Table 和 SQL API 中是完全安全的:
下面的结果可能是,从这个实现的数字可能会提高5% 。重用着提高了 Blink 规划的长度 (+53%) ,通常,您使用的功能。过多使用 SQL越多,启用对象重用的影响就显着吊坠。
没有对象重用 | 对象重用 | ||
老计划器 | 84,279 | → | 88,426 (+5%) |
blink计划器 | 89,048 | → | 136,710 (+54%) |
看看这些数字,你可能会想知道为什么启用了对 Blink 规划器的性能比旧规划器的性能要好得多。我们在此中大量使用了字符串查询:
如果对象重,使用相同任务的计划中的两个计划中使用的旧设备之间的数据交换,最终会在两个设备之间复制(String) Serializer。它最终会调用String # copy(复制) B 。如果您查看这些实现,您会发现StringSerializer(String)可以依赖于字符串的不变性,因此只是地地BinaryStringcopy( )需要通过Java复制有效的# emorySegment #复制内存Segment的字节,启用对象重用字节。
开始,除了寻找调整表 API 执行引擎和优化器之外,没有什么可以优化的了。然而,进一步给定的工作,调整从似乎都没有承诺改进这里。,实际上是您发现字符串序列化和序列化以及表API的类型访问对整体性能的影响最大,是对整个处理会的发现状态以正确的方式进行访问。
此外,当前定义的功能和功能可以直接用于接收和接收数据的新类型。一样将增强这两种类型。
跟上DataStream API
我的第一个连接,可能是天联,我可以通过将相同的方式实现真正的使用数据流API,而无需任何SQL层。这自然涉及更多的编码,我从使用 Java 开始。
第一次尝试
我的第一个大致的大致目标是代码行展开,其中的FactTable和DimensionTable是与上述SQL作业相同的源函数。将在LateralStreamJoin1 .java中找到它:
在使用适当的连接对流进行控制,事实表和元素键后一个地方连接。任何事件的维度对象,并且对于要存在于各个事件中的最新关键事件,它会采集最新关键数据,如果这个事件是任何事件。
总之,这将创建以下作业图,该作业图与上面的 SQL 作业非常相似,但不需要表转换,也不需要选择相应的键 - 这是内置在 join 函数中的,它为每个扩充使用Fact类的更改:Fact1 ,Fact2 , ...,Fact4 ,DenormalizedFact 。
到目前为止,一切都很好——无论有没有对象重用,我们都能比 Flink 的旧计划器高出大约 17%。然而,新的 Blink 规划器在启用对象重用后每秒能够挤出更多事件。实际上,对象重用不应该对我们的 DataStream 作业产生任何影响,因为没有链式操作符;以下数字中的 4% 差异似乎来自基准测试期间的正常波动。
没有对象重用 | 对象重用 | |
SQL 连接,旧规划器 | 84,279 | 88,426 |
SQL 连接,blink规划器 | 89,048 | 136,710 |
数据流连接 1 | 99,025 | 103,102 |
DataStream作业性能分析
我实际上期待更大的差异,所以让我们看一下分析器,看看 CPU 时间花在了哪里。您可以使用任何您喜欢的分析器;我展示了JMC 7的结果,并使用 Java 11 运行了LateralStreamJoin1以获得这些结果。如您所见,来自DataOutputSerializer、StringValue和反射访问的序列化工作超过了实际的业务逻辑,例如CopyOnWriteStateMap从堆上状态或com.ververica.LateralStreamJoin1 检索我们实际代码的匹配维度数据。
这并不奇怪,因为(反)序列化通常具有很高的总体成本,并且所呈现的作业本身没有(很多)计算 - 事实流中的每个事件基本上只有一个状态访问。另一方面,如果我们回顾上面的作业图,在每个步骤中都会对来自dim_table1的数据进行(反)序列化:join1、join2、join3、join4和join5,这是它唯一/第一个位置可以实际使用。这与其他维度表类似,似乎我们可以避免。
减少序列化开销
为了避免重复地连续(反)序列化相同的数据,我们可以在源处对其进行一次序列化,然后将其传递到真正需要它之前,即在join5任务中。使用我们的源生成器到达那里的最快方法是添加一个执行此转换的Map函数:
BinaryDimension是一个简单的POJO,其中一个long用于维度键,一个字节数组用于序列化数据。这是我们将要传递的对象。实际上,如果您查看 LateralStreamJoin2的代码,您会发现我们使用Tuple2 ,...,Tuple5 代替(为了简单起见)以及进一步(微小)的开销改进,因为 Tuple 序列化比序列化 POJO 快一点。因此,作业的主体仅通过具有这些附加映射、对参与类型的一些更改以及最终连接后的反序列化而略有变化。
尽管此代码在技术上不需要,但我们将连接表的反序列化保留为DenormalizedFact对象。在比较中删除它是不公平的,在任何实际工作中,您将继续使用这些数据或重新格式化它以获得正确的输出。
运行此修改版作业的分析器快照确实看起来不同:虽然顶级ThreadLocal仍然来自(反)序列化(称为StringValue.readString()),但相当多的 CPU 时间进入实际逻辑,例如CopyOnWriteStateMap。
这种额外的效率也反映在我们的基准数量上。在没有对象重用的情况下,DataStream 作业的优化版本现在比使用 Blink 计划器的 SQL 连接快大约 70%。进一步启用 对象重用减少了新映射运算符以及最后阶段(写入接收器)的开销,并提供了 13% 的优势。然而,Blink 规划器能够通过启用对象重用获得显着更大的增长 (+54%)。
最后,我们优化的 DataStream 作业因此比我们使用 SQL 所能达到的最佳速度快 28%。
没有对象重用 | 对象重用 | |
SQL 连接,旧规划器 | 84,279 | 88,426 |
SQL 连接,blink规划器 | 89,048 | 136,710 |
数据流连接 1 | 99,025 | 103,102 |
数据流连接 2 | 154,402 | 174,766 |
结论
与 DataStream API 相比,运行这个实验显示了 Blink 规划器的令人印象深刻的性能。我印象深刻的东西!此外,未来 对 Table API 的改进实际上可以进一步缩小差距,尤其是在 Table API 需要与 DataStream API 桥接的地方。使用 DataStream API 比 Blink 规划器进一步提高扩充作业的性能需要仔细考虑序列化堆栈并微调手头的特定作业。
所提出的技术基本上依赖于尽可能长时间地保持序列化形式的数据(通过网络传递时)并且仅在需要时对其进行反序列化。这可能会被包装在一些不错的实用方法/基类下,以提高结果代码的可读性和可用性。但是,当您查看进行优化所需的代码量时,与SQL的简单性相比,您还必须考虑可维护性和开发成本并决定权衡取舍。更重要的是,如果 Blink 规划器在未来引入了专门的维度连接,这种优化将在后台自动应用,而不需要对 SQL 查询本身进行任何更改。
往期推荐